/******************************************************************************
 *                     Organizacion de Datos - 75.06                          *
 *                         Catedra Lic. Servetto                              *
 *            Facultad de Ingenieria - Universidad de Buenos Aires            *
 ******************************************************************************/ 

#include "Serializador.h"
#include "Contenedor.h"
#include <sstream>
#include <iostream>
#include <bitset>

namespace CapaFisica
{

	SerializadorContenedores::SerializadorContenedores(Contenedor* contenedor)
	{

		this->contenedor = contenedor;	

	};

	int SerializadorContenedores::Serializar(char buffer[], int offset)
	{
		
		int pointer = offset;

		typedef list<Atributo>::const_iterator LIA;

		// Itera por cada Atributo y lo serializa a un array de bytes que se coloca en el buffer
		for ( LIA i = contenedor->Atributos->begin(); i != contenedor->Atributos->end() ; i++ ){

			Atributo a = *i;
			pointer = SerializarTipo(a,buffer,pointer);
		}

		typedef list<Contenedor>::iterator LIC;

		// Agrego un atributo interno para poder saber cuantos hijos tengo que deserializar
		Atributo* cantidadDeHijosInterno = new Atributo();
		cantidadDeHijosInterno->Tipo = 'E';
		int cantidadDeHijosInt = contenedor->Hijos->size();
		stringstream valor;
		valor << cantidadDeHijosInt;
		cantidadDeHijosInterno->Valor = new string(valor.str().c_str());
		pointer = SerializarTipo(*cantidadDeHijosInterno,buffer,pointer);

		// Itera por cada contenedor hijo y lo serializa a un array de bytes que se coloca en el buffer
		// Esta seccion de codigo es una llamada recursiva a los contenedores hijos.
		for (LIC i = contenedor->Hijos->begin(); i!=contenedor->Hijos->end();i++){

			Contenedor c = (*i);

			Serializador* serializador = new SerializadorContenedores(&c);
			
			int newPointer = serializador -> Serializar(buffer,pointer);
			// Calcula el tamaño en bytes del contenedor hijo
			(*i).Tamanio = newPointer - pointer;
			
			pointer = newPointer;
			
			delete serializador;
		}		
		// Calcula el tamaño en bytes del contenedor padre
		contenedor->Tamanio = pointer - offset;

		return pointer;

	};

	int SerializadorContenedores::Deserializar(char buffer[], int offset)
	{
		
		int pointer = offset;

		// Crea una nueva lista con los atributos modificados
		list<Atributo>* newAtributosList = new list<Atributo>();
		
		typedef list<Atributo>::const_iterator LIA;

		for ( LIA i = contenedor->Atributos->begin(); i != contenedor->Atributos->end(); i++ )
		{
			Atributo a = *i;
			pointer = DeSerializarTipo(a,buffer,pointer);
			// Guarda el atributo modificado en la nueva lista
			newAtributosList -> insert( newAtributosList -> end(), a );
		}

		// Libera la lista anterior de atributos y asigna la nueva
		delete contenedor->Atributos;
		contenedor->Atributos = newAtributosList;

		list<Contenedor>* newContenedoresList = new list<Contenedor>();
		
		typedef list<Contenedor>::const_iterator LIC;

		// Leo el atributo interno para poder saber cuantos hijos tengo que deserializar
		Atributo* cantidadDeHijosInterno = new Atributo();
		cantidadDeHijosInterno->Tipo = 'E';
		pointer = DeSerializarTipo(*cantidadDeHijosInterno,buffer,pointer);
		int hijos = atoi(cantidadDeHijosInterno->Valor->c_str());

		for (int j = 0; j < hijos; ++j)
		{
			Contenedor* newContendor = new Contenedor();
			list<Atributo>* newAtributosList = new list<Atributo>();

			for ( LIA i = (*contenedor->Hijos->begin()).Atributos->begin(); i != (*contenedor->Hijos->begin()).Atributos->end(); i++ )
			{
				Atributo a = *i;
				Atributo* newAtributo = new Atributo();
				newAtributo->Nombre = a.Nombre;
				newAtributo->Tipo = a.Tipo;
				newAtributo->Valor = a.Valor;
				newAtributosList -> insert( newAtributosList->end(), *newAtributo );
			}
			newContendor->Atributos = newAtributosList;
			newContendor->Hijos = new list<Contenedor>(); 
			
			Serializador* serializador = new SerializadorContenedores(newContendor);
			int newPointer = serializador->Deserializar(buffer,pointer);
			// Calcula el nuevo tamaño en bytes del contenedor hijo
			newContendor->Tamanio = newPointer - pointer;
			// Guarda el contenedor modificado en la nueva lista
			newContenedoresList -> insert( newContenedoresList -> end(), *newContendor );

			pointer = newPointer;

			delete serializador;
		}

		/*
		for ( LIC i = contenedor -> Hijos -> begin(); i != contenedor -> Hijos -> end(); i++ )
		{
			Contenedor c = *i;
			
			Serializador* serializador = new SerializadorContenedores(&c);
			int newPointer = serializador -> Deserializar(buffer,pointer);
			// Calcula el nuevo tamaño en bytes del contenedor hijo
			c.Tamanio = newPointer - pointer;
			// Guarda el contenedor modificado en la nueva lista
			newContenedoresList -> insert( newContenedoresList -> end(), c );

			pointer = newPointer;

			delete serializador;
		}
		*/
		
		// Libera la lista anterior de contenedores hijos y asigna la nueva
		delete contenedor -> Hijos;
		contenedor -> Hijos = newContenedoresList;
		
		// Calcula el nuevo tamaño en bytes del contenedor padre
		contenedor -> Tamanio = pointer - offset;

		return pointer;

	};

	int SerializadorContenedores::SerializarTipo(Atributo a, char buffer[], int pointer)
	{
		
		// Arrays auxiliares para la serializaci�n de tipos
		short auxShort[1];
		int auxInt[1];
		int largo;
		float auxFloat[1];		
		// Array de bytes utilizado para castear la serializacion
		char* byteArray;
				
		switch(a.Tipo){
			//Tipo Booleano
			case 'B':				
				if ( *(a.Valor) == "0" ) 
					buffer[pointer] = 0;
				else
					buffer[pointer] = 1;
				// Cada booleano se guarda como su representacion ASCII y ocupa un byte
				pointer++;
				break;

			//Tipo Caracter
			case 'C':
				// Lee el primer caracter del string valor.
				buffer[pointer] = a.Valor -> c_str()[0];

				pointer++;
				break;

			// Tipo Entero Corto (2 bytes)
			case 'E':
				// TODO: Chequear rango
				auxShort[0] = ( short ) atoi( a.Valor -> c_str() );
				// casteo dinamico de array de shorts a array de bytes
				byteArray = ( char * ) auxShort;
				// Coloca los bytes como big-endian
				buffer[pointer++] = byteArray[1];
				buffer[pointer++] = byteArray[0];				
				
				break;

			// Tipo Entero Largo (4 bytes)
			case 'L':

				auxInt[0] = atoi( a.Valor -> c_str() );
				// casteo dinamico de array de ints a array de bytes
				byteArray = ( char * ) auxInt;
				// Coloca los bytes como big-endian
				buffer[pointer++] = byteArray[3];
				buffer[pointer++] = byteArray[2];	
				buffer[pointer++] = byteArray[1];
				buffer[pointer++] = byteArray[0];	

				break;

			// Tipo Real (4 bytes)
			case 'R':
				// TODO: Chequear rango
				auxFloat[0] =  ( float ) atof( a.Valor -> c_str() );
				// casteo dinamico de array de floats a array de bytes
				byteArray = ( char * ) auxFloat;
				// Coloca los bytes como big-endian
				for ( int i=0; i < 4 ; i++ )
					buffer[pointer++] = byteArray[i];			

				break;
			// Tipo Fecha (String de 8 caracteres con el formato AAAAMMDD)
			case 'F':
				// TODO: chequear formato
				// Lee los 8 primer caracteres del string
				for ( int i=0; i < 10 ; i++ )
					buffer[pointer++] = a.Valor -> c_str()[i];

				break;
			// Tipo String (longitud variable)
			case 'S':
				//Coloca en el buffer un prefijo de longitud (maximo 255 caracteres)
				// TODO: chequar rango
				largo = a.Valor -> length();
				
				if (largo > 255)
				{
					largo = 225;
				}
				
				buffer[pointer++] = ( char ) largo;

				// Coloca los caracteres ASCII en el buffer, desde el primero al �ltimo
				for ( unsigned int i=0 ; i < a.Valor -> length() ; i++ )
					buffer[pointer++] = a.Valor -> c_str()[i];

				break;

			// TODO: crear Excepciones tipadas
			// Si es otro tipo tira una excepcion
			default:
				cerr << "CapaFisica->Serializador.cpp->SerializadorContenedores::SerializarTipo(Atributo a, char buffer[], int pointer)" << endl << "El tipo pasado no es valido." << endl;
				throw new exception();
		}		

		return pointer;

	}

	int SerializadorContenedores::DeSerializarTipo(Atributo &a, char buffer[], int pointer)
	{
		
		// Arrays auxiliares para la de-serializacion de tipos
		short auxShort;
		int auxInt;
		float auxFloat;
		// Array de bytes utilizado para castear la serializacion
		char* byteArray ;
		// Stream usado para convertir los array de bytes en su representacon como 
		// cadena de caracteres
		stringstream* oss = new stringstream( ios_base::out );

		// Libera el valor anterior
		delete a.Valor;
		
		switch(a.Tipo){
			//Tipo Booleano
			case 'B':				
				// Se consiera '0' si el char es ceroy '1' si es cualquier otro valor
				if (buffer[pointer++] == 0)
					a.Valor = new string(1,'0');
				else
					a.Valor = new string(1,'1');

				break;
				
			//Tipo Caracter
			case 'C':
				// Genera un nuevo string con el char del buffer
				a.Valor = new string(1,buffer[pointer++]);
				
				break;
			
			// Entero Corto
			case 'E':
				// Lee dos bytes del buffer, e incrementa la posici�n del puntero
				byteArray = new char[2];
				
				byteArray[1] = buffer[pointer++];
				byteArray[0] = buffer[pointer++];

				// Reinterpreta los dos bytes como un short
				auxShort = reinterpret_cast< short& > ( *byteArray );

				// Escribe el valor short en el stream
				*oss << auxShort;				
				
				// Crea un nuevo string para valor a partir del stream
				a.Valor = new string( oss -> str().c_str() );				
				
				break;

			// Tipo Entero
			case 'L':
				// Lee cuatro bytes del buffer, e incrementa la posici�n del puntero
				byteArray = new char[4];
				
				byteArray[3] = buffer[pointer++];
				byteArray[2] = buffer[pointer++];
				byteArray[1] = buffer[pointer++];
				byteArray[0] = buffer[pointer++];

				// Reinterpreta los cuatro bytes como un int
				auxInt = reinterpret_cast< int& > ( *byteArray );

				// Escribe el valor short en el stream
				*oss << auxInt;
				
				// Crea un nuevo string para valor a partir del stream
				a.Valor = new string( oss -> str().c_str() );

				break;

			// Tipo Real
			case 'R':
				// Lee cuatro bytes del buffer, e incrementa la posici�n del puntero
				byteArray = new char[4];
				
				for (int i=0; i<4; i++)
					byteArray[i] = buffer[pointer++];

				// Reinterpreta los cuatro bytes como un float
				auxFloat = reinterpret_cast< float& >( *byteArray );

				// Escribe el valor short en el stream
				*oss << auxFloat;

				// Crea un nuevo string para valor a partir del stream
				a.Valor = new string( oss -> str().c_str() );

				break;
			// Tipo Fecha
			case 'F':
				// Lee 8 bytes del buffer, incrementa la posicion del puntero
				// y los coloca en el stream como caracteres ASCII
				for (int i=0; i<10; i++)
					*oss << buffer[pointer++];
				
				// Crea un nuevo string para valor a partir del stream
				a.Valor = new string( oss -> str().c_str() );

				break;

			// Tipo String
			case 'S':
				// Lee el prefijo de longitud
				unsigned char longitud = buffer[pointer++];

				// Lee la cantidad 'longitud' de caracteres y los coloca en el stream
				// como caracteres ASCII
				for ( int i=0 ; i < longitud ; i++ )
					*oss << buffer[pointer++];
				
				// Crea un nuevo string para valor a partir del stream
				a.Valor = new string( oss -> str().c_str() );

				break;
		}

		// Libera recursos
		delete oss;		

		return pointer;

	}

}
